using System;
using System.IO;

namespace FileMapper {
    /*
     * Multiple MapperStreams for a single Stream insance are not supported.
     */
    public class MapperStream: Stream {
        protected Stream stream;
        protected long position;
        protected long length;

        #region Properties
        #region Inherited Properties
        public override bool CanRead {
            get { return stream.CanRead; }
        }

        public override bool CanSeek {
            get { return stream.CanSeek; }
        }

        public override bool CanWrite {
            get { return stream.CanWrite; }
        }

        public override long Length {
            get { return length; }
        }

        public override long Position {
            get { return position; }
            set {
                position = value;
                long? mappedPos = Mapper.MapPosition(value);
                if (mappedPos != null) {
                    stream.Position = (long)(mappedPos);
                }
            }
        }
        #endregion

        public Mapper Mapper { get; set; }
        #endregion

		public MapperStream(Stream stream, long origLength):
			this(stream,
			     new Mapper {
				 	 OldExtents = new ExtentList { new Extent { Offset = 0, Size = origLength } },
					 NewExtents = new ExtentList { new Extent { Offset = 0, Size = origLength } }
				 },
				 origLength)
		{}
		
        public MapperStream(Stream stream, Mapper mapper, long origLength) {
            this.stream = stream;
            this.Mapper = mapper;
            this.length = origLength;
        }

        #region Methods
        #region Inherited Methods
        public override void Flush() {
            stream.Flush();
        }

        public override int Read(byte[] buffer, int offset, int count) {
            Console.WriteLine("Begin read\n\toffset: {0}->{1}\n\tcount: {2}", Position, stream.Position, count);
            var savedPos = Position;
            int result = 0;
            try {
                var exts = Mapper.MapExtent(new Extent { Offset = Position, Size = count });
                long offs = offset;
                Console.WriteLine(exts.ToString());
                foreach (var ext in exts) {
                    if (ext.Offset == null) {
                        for (long i = 0; i < ext.Size; ++i) {
                            buffer[offs + i] = 0x00;
                        }
                        result += (int)(ext.Size);
                    } else {
                        stream.Position = (long)(ext.Offset);
                        var part = stream.Read(buffer, (int)(offs), (int)(ext.Size));
                        result += part;
                        if (part < ext.Size) {
                            break;
                        }
                    }
                    offs += ext.Size;
                }
                return result;
            } finally {
                Position = savedPos + result;
                Console.WriteLine("End read\n\toffset: {0}->{1}\n\tcount: {2}", Position, stream.Position, result);
            }
        }

        public override long Seek(long offset, SeekOrigin origin) {
            switch (origin) {
                case SeekOrigin.Begin:
                    Position = offset;
                    break;
                case SeekOrigin.Current:
                    Position += offset;
                    break;
                case SeekOrigin.End:
                    Position = Length + offset;
                    break;
            }
            return Position;
        }

        public override void SetLength(long value) {
            // TODO implement SetLength
            stream.SetLength(value);
        }

        public override void Write(byte[] buffer, int offset, int count) {
            var savedPos = Position;
            try {
                var exts = Mapper.MapExtent(new Extent { Offset = Position, Size = count });
                long offs = offset;
                foreach (var ext in exts) {
                    if (ext.Offset != null) {
                        stream.Position = (long)(ext.Offset);
                        stream.Write(buffer, (int)(offs), (int)(ext.Size));
                    }
                    offs += ext.Size;
                }
            } finally {
                Position = savedPos + count;
            }
        }
        #endregion
        #endregion
    }
}
